// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="ActionLogService.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the ActionLogService type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.EventService.Services
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;

    using ScrumForTeamSystem.EventService.Infrastructure;
    using ScrumForTeamSystem.EventService.Infrastructure.Interfaces;
    using ScrumForTeamSystem.EventService.Services.Properties;

    /// <summary>
    /// Defines the ActionLogService type.
    /// </summary>
    internal class ActionLogService : IActionLoggingService, IDisposable
    {
        /// <summary>
        /// The name of the event log source
        /// </summary>
        private const string Source = "Scrum for Team System";

        /// <summary>
        /// The name of the event log
        /// </summary>
        private const string Log = "Application";

        /// <summary>
        /// The message queue.
        /// </summary>
        private readonly Queue<string> messageQueue = new Queue<string>();

        /// <summary>
        /// The queue processing work method.
        /// </summary>
        private BackgroundWorker queueProcesor;

        /// <summary>
        /// The is running flag.
        /// </summary>
        private bool isRunning;

        /// <summary>
        /// Gets a value indicating whether this instance has output path.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has output path; otherwise, <c>false</c>.
        /// </value>
        public bool HasValidOutputPath
        {
            get
            {
                return !String.IsNullOrEmpty(Properties.Settings.Default.LogFilePath)
                       && File.Exists(Properties.Settings.Default.LogFilePath);
            }
        }

        /// <summary>
        /// Sets the event message.
        /// </summary>
        /// <param name="eventMessage">The event message.</param>
        public void LogEventMessage(string eventMessage)
        {
            this.LogMessage(eventMessage);
        }

        /// <summary>
        /// Registers the action creator.
        /// </summary>
        /// <param name="eventCreator">
        /// The event creator.
        /// </param>
        public void RegisterLogGenerator(IMessageProducer eventCreator)
        {
            if (eventCreator == null)
            {
                throw new ArgumentNullException("eventCreator");
            }

            eventCreator.Message += this.EventCreatorLogEvent;
            eventCreator.ExceptionMessage += this.EventCreatorLogException;
        }

        /// <summary>
        /// Adds the exception to event log.
        /// </summary>
        /// <param name="ex">The exception to log.</param>
        public void WriteToEventLog(Exception ex)
        {
            if (ex == null)
            {
                throw new ArgumentNullException("ex");
            }

            var message = string.Format(CultureInfo.InvariantCulture, Resources.String004, ex.GetType().Name);
            var stack = ex.StackTrace;

            while (ex != null)
            {
                message = string.Concat(message, Environment.NewLine, ex.Message);
                ex = ex.InnerException;
            }

            if (!string.IsNullOrEmpty(stack))
            {
                message = string.Concat(message, Environment.NewLine, stack);
            }

            this.WriteToEventLog(message, EventLogEntryType.Error);
        }

        /// <summary>
        /// Writes to event log.
        /// </summary>
        /// <param name="message">The message.</param>
        public void WriteToEventLog(string message)
        {
            this.WriteToEventLog(message, EventLogEntryType.Information);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Called when [do work].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
        private static void OnDoWork(object sender, DoWorkEventArgs e)
        {
            var queue = e.Argument as Queue<string>;

            if (queue == null)
            {
                return;
            }

            using (var sw = new StreamWriter(Properties.Settings.Default.LogFilePath, true))
            {
                while (queue.Count != 0)
                {
                    var message = queue.Dequeue();
                    sw.WriteLine(
                            string.Concat(
                                    DateTime.Now.ToString(Settings.Default.LogFileDateTimeFormat, CultureInfo.InvariantCulture),
                                    Resources.String001,
                                    message));
                }
            }
        }

        /// <summary>
        /// Write to event log.
        /// </summary>
        /// <param name="message">The log message.</param>
        /// <param name="logEntryType">The event log message type.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method needs to trap all exceptions in order to write to event log.")]
        private void WriteToEventLog(string message, EventLogEntryType logEntryType)
        {
            Debug.WriteLine(string.Concat("Event Log Message: ", message));

            try
            {
                if (!EventLog.SourceExists(Source))
                {
                    EventLog.CreateEventSource(Source, Log);
                }

                EventLog.WriteEntry(Source, message, logEntryType);
            }
            catch (Exception ex)
            {
                var exceptionMessage = string.Empty;
                while (ex != null)
                {
                    if (!string.IsNullOrEmpty(exceptionMessage))
                    {
                        exceptionMessage = string.Concat(exceptionMessage, Environment.NewLine);
                    }

                    exceptionMessage = string.Concat(exceptionMessage, ex.Message);
                    ex = ex.InnerException;
                }

                // Try and send the exception detail to the log file.
                this.LogMessage(string.Concat(Resources.String005, exceptionMessage));
            }
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void LogMessage(string message)
        {
            Debug.WriteLine(message);

            if (!this.HasValidOutputPath)
            {
                return;
            }

            this.messageQueue.Enqueue(message);
            this.StartQueueProcessor();
        }

        /// <summary>
        /// Starts the queue processor.
        /// </summary>
        private void StartQueueProcessor()
        {
            if (this.isRunning)
            {
                return;
            }

            this.isRunning = true;

            if (this.queueProcesor == null)
            {
                this.queueProcesor = new BackgroundWorker();
                this.queueProcesor.DoWork += OnDoWork;
                this.queueProcesor.RunWorkerCompleted += this.OnRunWorkerCompleted;
            }

            this.queueProcesor.RunWorkerAsync(this.messageQueue);
        }

        /// <summary>
        /// Called when [run worker completed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
        private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.isRunning = false;
        }

        /// <summary>
        /// Handles the LogEvent event of the eventCreator control.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        private void EventCreatorLogEvent(object sender, ActionEventArgs e)
        {
            this.LogMessage(string.Concat(sender.GetType().FullName, Resources.String001, e.EventMessage));
        }

        /// <summary>
        /// Handles the LogException event of the eventCreator control.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        private void EventCreatorLogException(object sender, ExceptionActionEventArgs e)
        {
            this.WriteToEventLog(e.Exception);

            var errorMsg = String.Empty;
            var stackTrace = String.Empty;

            var ex = e.Exception;

            if (!String.IsNullOrEmpty(ex.StackTrace))
            {
                stackTrace = ex.StackTrace;
            }

            while (ex != null)
            {
                errorMsg += ex.Message;
                ex = ex.InnerException;
            }

            this.LogMessage(string.Concat(sender.GetType().FullName, Resources.String002, errorMsg, Environment.NewLine, stackTrace));
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.queueProcesor != null)
                {
                    this.queueProcesor.DoWork -= OnDoWork;
                    this.queueProcesor.RunWorkerCompleted -= this.OnRunWorkerCompleted;
                    this.queueProcesor.Dispose();
                    this.queueProcesor = null;
                }
            }
        }
    }
}